﻿/*  ------------------------------------------------------------------------
	 NQ Common Services Library
	 
	 Homepage: http://www.awzhome.de/
	 ------------------------------------------------------------------------
	 
	 The contents of this file are subject to the Mozilla Public License
	 Version 1.1 (the "License"); you may not use this file except in
	 compliance with the License. You may obtain a copy of the License at
	 http://www.mozilla.org/MPL/
	 
	 Software distributed under the License is distributed on an "AS IS"
	 basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
	 License for the specific language governing rights and limitations
	 under the License.
	
	 The Original Code is code of NQ Common Services Library.

	 The Initial Developer of the Original Code is Andreas Weizel.
	 Portions created by the Initial Developer are
	 Copyright (C) 2011-2012 Andreas Weizel. All Rights Reserved.
	 
	 Contributor(s): (none)
	 
	 Alternatively, the contents of this file may be used under the terms
	 of the GNU General Public License Version 2.0 or later (the "GPL"),
	 or the GNU Lesser General Public License Version 2.1 or later (the
	 "LGPL"), in which case the provisions of the GPL or the LGPL are
	 applicable instead of those above. If you wish to allow use of your
	 version of this file only under the terms of the GPL or the LGPL and
	 not to allow others to use your version of this file under the MPL,
	 indicate your decision by deleting the provisions above and replace
	 them with the notice and other provisions required by the GPL or the
	 LGPL. If you do not delete the provisions above, a recipient may use
	 your version of this file under the terms of any of the MPL, the GPL
	 or the LGPL.
	 ------------------------------------------------------------------------
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AWZhome.NQ.CommonServices.Helpers;
using AWZhome.NQ.Core;
using System.IO;
using AWZhome.NQ.Core.Fluent;
using System.Security.Cryptography;
using System.Xml;
using System.Xml.Linq;

namespace AWZhome.NQ.CommonServices
{
	[NQExportedService(SingleInstance = true)]
	public class ConfigService : Services.IConfigService, IDisposable
	{
		private object _sync;

		private Dictionary<string, ConfigCategory> _userCategories = new Dictionary<string, ConfigCategory>();
		private Dictionary<string, ConfigCategory> _globalCategories = new Dictionary<string, ConfigCategory>();

		private string _userConfFileName;
		private string _globalConfFileName;
		private string _configurationName = null;

		public ConfigService()
		{
			// Create synchronization object
			_sync = NQServiceManager.Instance.SyncObject;
		}

		public void Dispose()
		{
			// Try to save the files
			this.SaveConfiguration();
		}

		#region INQConfigService Member

		public string UserAppDataPath
		{
			get
			{
				return Path.Combine(Environment.GetFolderPath(
					Environment.SpecialFolder.ApplicationData), _configurationName);
			}
		}

		public string CommonAppDataPath
		{
			get
			{
				return Path.Combine(Environment.GetFolderPath(
					Environment.SpecialFolder.CommonApplicationData), _configurationName);
			}
		}

		public string[] GetCategoryList()
		{
			lock (_sync)
			{
				// Combine global and user-specific categories
				return _userCategories.Keys.Union(_globalCategories.Keys).ToArray();
			}
		}

		public string[] GetGroupList(string category)
		{
			lock (_sync)
			{
				bool isUserCategory = _userCategories.ContainsKey(category);
				bool isGlobalCategory = _globalCategories.ContainsKey(category);
				if (isUserCategory && isGlobalCategory)
				{
					// Combine groups of global and user-specific settings
					return _userCategories[category].Groups.Keys.Union(_globalCategories[category].Groups.Keys).ToArray();
				}
				else if (isUserCategory)
				{
					// Return groups from user category
					return _userCategories[category].Groups.Keys.ToArray();
				}
				else if (isGlobalCategory)
				{
					// Return groups from global category
					return _globalCategories[category].Groups.Keys.ToArray();
				}
				else
				{
					// Category not found in any configuration
					return null;
				}
			}
		}

		public string[] GetSettingList(string category, string group, bool includeValueSettings, bool includeListSettings)
		{
			lock (_sync)
			{
				List<string> settingList = new List<string>();

				// Look for user-specific settings
				if (_userCategories.ContainsKey(category))
				{
					ConfigCategory configCategory = _userCategories[category];
					if (configCategory.Groups.ContainsKey(group))
					{
						ConfigGroup configGroup = configCategory.Groups[group];
						if (includeValueSettings && configGroup.Values.Count > 0)
						{
							settingList.AddRange(configGroup.Values.Keys);
						}
						if (includeListSettings && configGroup.Lists.Count > 0)
						{
							settingList.AddRange(configGroup.Lists.Keys);
						}
					}
				}
				// Now the same with global settings
				if (_globalCategories.ContainsKey(category))
				{
					ConfigCategory configCategory = _globalCategories[category];
					if (configCategory.Groups.ContainsKey(group))
					{
						ConfigGroup configGroup = configCategory.Groups[group];
						if (includeValueSettings && configGroup.Values.Count > 0)
						{
							settingList.AddRange(configGroup.Values.Keys);
						}
						if (includeListSettings && configGroup.Lists.Count > 0)
						{
							settingList.AddRange(configGroup.Lists.Keys);
						}
					}
				}

				return settingList.ToArray();
			}
		}

		public ConfigData GetSetting(string category, string group, string settingName)
		{
			lock (_sync)
			{
				// Look for user-specific settings
				if (_userCategories.ContainsKey(category))
				{
					ConfigCategory configCategory = _userCategories[category];
					if (configCategory.Groups.ContainsKey(group))
					{
						ConfigGroup configGroup = configCategory.Groups[group];
						if (configGroup.Values.ContainsKey(settingName))
						{
							return configGroup.Values[settingName];
						}
					}
				}

				// Now the same with global settings
				if (_globalCategories.ContainsKey(category))
				{
					ConfigCategory configCategory = _globalCategories[category];
					if (configCategory.Groups.ContainsKey(group))
					{
						ConfigGroup configGroup = configCategory.Groups[group];
						if (configGroup.Values.ContainsKey(settingName))
						{
							return configGroup.Values[settingName];
						}
					}
				}

				// Setting hasn't been found so far, return null
				return null;
			}
		}

		public IList<ConfigData> GetListSetting(string category, string group, string settingName)
		{
			lock (_sync)
			{
				// Look for user-specific settings
				if (_userCategories.ContainsKey(category))
				{
					ConfigCategory configCategory = _userCategories[category];
					if (configCategory.Groups.ContainsKey(group))
					{
						ConfigGroup configGroup = configCategory.Groups[group];
						if (configGroup.Lists.ContainsKey(settingName))
						{
							return configGroup.Lists[settingName];
						}
					}
				}

				// Now the same with global settings
				if (_globalCategories.ContainsKey(category))
				{
					ConfigCategory configCategory = _globalCategories[category];
					if (configCategory.Groups.ContainsKey(group))
					{
						ConfigGroup configGroup = configCategory.Groups[group];
						if (configGroup.Lists.ContainsKey(settingName))
						{
							return configGroup.Lists[settingName];
						}
					}
				}

				// Setting hasn't been found so far, return null
				return null;
			}
		}

		public bool SetSetting(string category, string group, string settingName, ConfigData data, bool userSetting)
		{
			lock (_sync)
			{
				// Select the requested category list (user-specific or global)
				Dictionary<string, ConfigCategory> categoryList = null;
				if (userSetting)
				{
					categoryList = _userCategories;
				}
				else
				{
					categoryList = _globalCategories;
				}

				if (categoryList != null)
				{
					if (!categoryList.ContainsKey(category))
					{
						// Automatically add missing category
						categoryList.Add(category, new ConfigCategory());
					}
					ConfigCategory categoryItem = categoryList[category];
					if (categoryItem != null)
					{
						if (!categoryItem.Groups.ContainsKey(group))
						{
							// Automatically add missing group
							categoryItem.Groups.Add(group, new ConfigGroup());
						}
						ConfigGroup groupItem = categoryItem.Groups[group];
						if (groupItem != null)
						{
							// Save the new setting value
							groupItem.Values[settingName] = data;
						}
					}
				}

				return true;
			}
		}

		public bool SetListSetting(string category, string group, string settingName, IList<ConfigData> data, bool userSetting)
		{
			lock (_sync)
			{
				// Select the requested category list (user-specific or global)
				Dictionary<string, ConfigCategory> categoryList = null;
				if (userSetting)
				{
					categoryList = _userCategories;
				}
				else
				{
					categoryList = _globalCategories;
				}

				if (categoryList != null)
				{
					if (!categoryList.ContainsKey(category))
					{
						// Automatically add missing category
						categoryList.Add(category, new ConfigCategory());
					}
					ConfigCategory categoryItem = categoryList[category];
					if (categoryItem != null)
					{
						if (!categoryItem.Groups.ContainsKey(group))
						{
							// Automatically add missing group
							categoryItem.Groups.Add(group, new ConfigGroup());
						}
						ConfigGroup groupItem = categoryItem.Groups[group];
						if (groupItem != null)
						{
							// Save the new setting value
							groupItem.Lists[settingName] = data;
						}
					}
				}

				return true;
			}
		}

		public bool RemoveSetting(string category, string group, string settingName, bool userSetting)
		{
			lock (_sync)
			{
				// Select the requested category list (user-specific or global)
				Dictionary<string, ConfigCategory> categoryList = null;
				if (userSetting)
				{
					categoryList = _userCategories;
				}
				else
				{
					categoryList = _globalCategories;
				}

				if ((categoryList != null) && categoryList.ContainsKey(category))
				{
					ConfigCategory categoryItem = categoryList[category];
					if ((categoryItem != null) && categoryItem.Groups.ContainsKey(group))
					{
						ConfigGroup groupItem = categoryItem.Groups[group];
						if (groupItem != null)
						{
							// Check whether the item to delete is existing at all
							if (groupItem.Values.ContainsKey(settingName))
							{
								groupItem.Values.Remove(settingName);

								// Also delete the group, if no more values are in it
								if ((groupItem.Values.Count == 0) && (groupItem.Lists.Count == 0))
								{
									categoryItem.Groups.Remove(group);
								}

								// Also delete the category, if no more groups are in it
								if (categoryItem.Groups.Count == 0)
								{
									categoryList.Remove(category);
								}

								return true;
							}
						}
					}
				}

				return false;
			}
		}

		public bool RemoveListSetting(string category, string group, string settingName, bool userSetting)
		{
			lock (_sync)
			{
				// Select the requested category list (user-specific or global)
				Dictionary<string, ConfigCategory> categoryList = null;
				if (userSetting)
				{
					categoryList = _userCategories;
				}
				else
				{
					categoryList = _globalCategories;
				}

				if ((categoryList != null) && categoryList.ContainsKey(category))
				{
					ConfigCategory categoryItem = categoryList[category];
					if ((categoryItem != null) && categoryItem.Groups.ContainsKey(group))
					{
						ConfigGroup groupItem = categoryItem.Groups[group];
						if (groupItem != null)
						{
							// Check whether the item to delete is existing at all
							if (groupItem.Lists.ContainsKey(settingName))
							{
								groupItem.Lists.Remove(settingName);

								// Also delete the group, if no more values are in it
								if ((groupItem.Values.Count == 0) && (groupItem.Lists.Count == 0))
								{
									categoryItem.Groups.Remove(group);
								}

								// Also delete the category, if no more groups are in it
								if (categoryItem.Groups.Count == 0)
								{
									categoryList.Remove(category);
								}

								return true;
							}
						}
					}
				}

				return false;
			}
		}

		public bool RenameGroup(string category, string oldGroupName, string newGroupName, bool userSetting)
		{
			lock (_sync)
			{
				// Select the requested category list (user-specific or global)
				Dictionary<string, ConfigCategory> categoryList = null;
				if (userSetting)
				{
					categoryList = _userCategories;
				}
				else
				{
					categoryList = _globalCategories;
				}

				if ((categoryList != null) && categoryList.ContainsKey(category))
				{
					ConfigCategory categoryItem = categoryList[category];
					if ((categoryItem != null) && categoryItem.Groups.ContainsKey(oldGroupName))
					{
						ConfigGroup groupItem = categoryItem.Groups[oldGroupName];
						if (groupItem != null)
						{
							categoryItem.Groups.Remove(oldGroupName);
							categoryItem.Groups.Add(newGroupName, groupItem);
							return true;
						}
					}
				}

				return false;
			}
		}

		public bool RemoveGroup(string category, string group, bool userSetting)
		{
			lock (_sync)
			{
				// Select the requested category list (user-specific or global)
				Dictionary<string, ConfigCategory> categoryList = null;
				if (userSetting)
				{
					categoryList = _userCategories;
				}
				else
				{
					categoryList = _globalCategories;
				}

				if ((categoryList != null) && categoryList.ContainsKey(category))
				{
					ConfigCategory categoryItem = categoryList[category];
					if ((categoryItem != null) && categoryItem.Groups.ContainsKey(group))
					{
						categoryItem.Groups.Remove(group);
						return true;
					}
				}

				return false;
			}
		}

		public void LoadConfiguration()
		{
			lock (_sync)
			{
				// If no configuration name has been set, use default
				if (_configurationName == null)
				{
					this.SetConfigurationName("NQ");
				}

				EnsureUserDirExists();
				LoadXMLConfiguration(_globalConfFileName, _globalCategories);
				LoadXMLConfiguration(_userConfFileName, _userCategories);
			}
		}

		public void SaveConfiguration()
		{
			lock (_sync)
			{
				// If no configuration name has been set, use default
				if (_configurationName == null)
				{
					this.SetConfigurationName("NQ");
				}

				SaveXMLConfiguration(_globalConfFileName, _globalCategories);
				SaveXMLConfiguration(_userConfFileName, _userCategories);
			}
		}

		public void SetConfigurationName(string configurationName)
		{
			lock (_sync)
			{
				_configurationName = configurationName;
				// Define configuration directories and file paths
				this.HandleConfigPath();
			}
		}

		#endregion

		#region Private methods

		private bool LoadXMLConfiguration(string sourceFile, Dictionary<string, ConfigCategory> categoryList)
		{
			if (File.Exists(sourceFile))
			{
				IResourceManager resourceService = ServiceCore
					.Get<Services.IResourceService>()
					.To;

				try
				{
					// Open the XML document in DOM
					XDocument doc = XDocument.Load(sourceFile);
					if ((doc != null) && (doc.Root.Name == "NQConfiguration"))
					{
						var versionAttrib = doc.Root.Attribute("version");
						if ((versionAttrib != null) && (versionAttrib.Value == "1.0"))
						{
							foreach (var categoryNode in doc.Descendants("category"))
							{
								var categoryNameAttrib = categoryNode.Attribute("name");
								if (categoryNameAttrib != null)
								{
									// Create the category
									ConfigCategory configCategory;
									if (categoryList.ContainsKey(categoryNameAttrib.Value))
									{
										configCategory = categoryList[categoryNameAttrib.Value];
									}
									else
									{
										configCategory = new ConfigCategory();
										categoryList.Add(categoryNameAttrib.Value, configCategory);
									}

									foreach (var groupNode in categoryNode.Descendants("group"))
									{
										var groupNameAttrib = groupNode.Attribute("name");
										if (groupNameAttrib != null)
										{
											// Create the group
											ConfigGroup configGroup;
											if (configCategory.Groups.ContainsKey(groupNameAttrib.Value))
											{
												configGroup = configCategory.Groups[groupNameAttrib.Value];
											}
											else
											{
												configGroup = new ConfigGroup();
												configCategory.Groups.Add(groupNameAttrib.Value, configGroup);
											}

											// Enumerate all single-value settings
											foreach (var valueNode in groupNode.Descendants("value"))
											{
												var valueNameAttrib = valueNode.Attribute("name");
												if (valueNameAttrib != null)
												{
													ConfigData data = new ConfigData()
													{
														RawData = valueNode.Value
													};
													configGroup.Values[valueNameAttrib.Value] = data;
												}
											}

											// Enumerate all list settings
											foreach (var listNode in groupNode.Descendants("list"))
											{
												var listNameAttrib = listNode.Attribute("name");
												if (listNameAttrib != null)
												{
													List<ConfigData> datalist = new List<ConfigData>();
													// Enumerate all list elements
													foreach (var valueNode in listNode.Descendants("value"))
													{
														ConfigData data = new ConfigData()
														{
															RawData = valueNode.Value
														};
														datalist.Add(data);
													}
													configGroup.Lists[listNameAttrib.Value] = datalist;
												}
											}
										}
									}
								}
							}
						}
					}
				}
				catch (UnauthorizedAccessException)
				{
					ServiceCore.Log.Error(resourceService.GetResString("NQCS", "AppErr.NoReadPermUserSettFile"));
				}
				catch (Exception ex)
				{
					ServiceCore.Log.Error(resourceService.GetResString("NQCS", "AppErr.CantReadUserSettFile", ex.Message));
				}
			}
			else
			{
				return false;
			}

			return true;
		}

		private bool SaveXMLConfiguration(string targetFile, Dictionary<string, ConfigCategory> categoryList)
		{
			IResourceManager resourceService = ServiceCore
				.Get<Services.IResourceService>()
				.To;

			// Create a new XML document
			XElement root = new XElement("NQConfiguration");
			XDocument doc = new XDocument(root);

			// Add version
			root.SetAttributeValue("version", "1.0");

			// Add comment with current working directory
			XComment workingDirComment = new XComment(Environment.CurrentDirectory);
			root.Add(workingDirComment);

			// Add categories
			foreach (var categoryKeyValue in categoryList)
			{
				XElement categoryNode = new XElement("category");
				categoryNode.SetAttributeValue("name", categoryKeyValue.Key);
				root.Add(categoryNode);

				// Add groups
				foreach (var groupKeyValue in categoryKeyValue.Value.Groups)
				{
					XElement groupNode = new XElement("group");
					groupNode.SetAttributeValue("name", groupKeyValue.Key);
					categoryNode.Add(groupNode);

					// Add single-value settings
					foreach (var valueKeyValue in groupKeyValue.Value.Values)
					{
						XElement valueNode = new XElement("value");
						valueNode.SetAttributeValue("name", valueKeyValue.Key);
						valueNode.Value = valueKeyValue.Value.RawData ?? "";
						groupNode.Add(valueNode);
					}

					// Add list settings
					foreach (var listKeyValue in groupKeyValue.Value.Lists)
					{
						XElement listNode = new XElement("list");
						listNode.SetAttributeValue("name", listKeyValue.Key);
						groupNode.Add(listNode);

						// Add elements of the list setting
						foreach (var valueElement in listKeyValue.Value)
						{
							XElement valueNode = new XElement("value");
							valueNode.Value = valueElement.RawData ?? "";
							listNode.Add(valueNode);
						}
					}
				}
			}

			try
			{
				// Eventually save the XML document
				doc.Save(targetFile);
				return true;
			}
			catch (UnauthorizedAccessException)
			{
				ServiceCore.Log
					.From(this)
					.Error(resourceService.GetResString("NQCS", "AppErr.NoWritePermUserSettFile"));
				return false;
			}
			catch (Exception ex)
			{
				ServiceCore.Log
					.From(this)
					.Error(resourceService.GetResString("AppErr.CantWriteUserSettFile", ex.Message));
				return false;
			}
		}

		private void HandleConfigPath()
		{
			// Handle global configuration file
			string[] xmlfilecmd = NQServiceManager.Instance.GetCmdLineParam("sfg");
			if ((xmlfilecmd != null) && (xmlfilecmd.Length > 0))
			{
				_globalConfFileName = xmlfilecmd[0];
				if (!Path.IsPathRooted(_globalConfFileName))
				{
					_globalConfFileName = Path.Combine(this.CommonAppDataPath, _globalConfFileName);
				}
			}
			else
			{
				// The setting file's name contains a hash of application directory by default
				_globalConfFileName = _configurationName + "." + this.GetConfFileMD5Hash(Environment.CurrentDirectory) + ".xml";
				_globalConfFileName = Path.Combine(this.CommonAppDataPath, _globalConfFileName);
			}

			// Handle user configuration file
			xmlfilecmd = NQServiceManager.Instance.GetCmdLineParam("sfu");
			if ((xmlfilecmd != null) && (xmlfilecmd.Length > 0))
			{
				_userConfFileName = xmlfilecmd[0];
				if (!Path.IsPathRooted(_userConfFileName))
				{
					_userConfFileName = Path.Combine(this.UserAppDataPath, _userConfFileName);
				}
			}
			else
			{
				// The setting file's name contains a hash of application directory by default
				_userConfFileName = _configurationName + "." + this.GetConfFileMD5Hash(Environment.CurrentDirectory) + ".xml";
				_userConfFileName = Path.Combine(this.UserAppDataPath, _userConfFileName);
			}
		}

		private bool EnsureUserDirExists()
		{
			// Get needed services
			IResourceManager servRes = ServiceCore
				.Get<Services.IResourceService>()
				.To;
			if (servRes == null)
				return false;

			// Ensure the directory exists
			try
			{
				if (!Directory.Exists(this.CommonAppDataPath))
				{
					// Attempt to create common directory
					Directory.CreateDirectory(this.CommonAppDataPath);
				}

				if (!Directory.Exists(this.UserAppDataPath))
				{
					// Attempt to create user directory
					Directory.CreateDirectory(this.UserAppDataPath);
				}
				return true;
			}
			catch (UnauthorizedAccessException)
			{
				ServiceCore.Log
					.From(this)
					.Error(servRes.GetResString("NQCS", "AppErr.NoPermUserSettDir"));
				return false;
			}
			catch (Exception ex)
			{
				ServiceCore.Log
					.From(this)
					.Error(servRes.GetResString("AppErr.CantCreateUserSettDir", ex.Message));
				return false;
			}
		}

		private string CombineProfileName(string protocolName, string profileName)
		{
			return protocolName + "|" + profileName;
		}

		private string GetConfFileMD5Hash(string source)
		{
			MD5 md5dir = MD5.Create();
			byte[] sourcebytes = Encoding.UTF8.GetBytes(source.ToLower());
			byte[] hashbytes = md5dir.ComputeHash(sourcebytes);
			StringBuilder result = new StringBuilder();
			for (int i = 0; i < hashbytes.Length; i++)
			{
				result.Append(hashbytes[i].ToString("x2"));
			}

			return result.ToString();
		}

		#endregion
	}
}
